#ifndef MESH_BASE_HPP
#define MESH_BASE_HPP

#include "meshTypes_fwd.hpp"
#include "crtpBase.hpp"
#include "logger.hpp"
#include "boundaryPatch.hpp"
#include "edgesBase.hpp"
#include "cellsBase.hpp"

namespace hms {

/* this is not a forward declaration, but rather the full definition of the
 * unspecialised traits class. Each mesh implementation has to specialise it.
 * */
/**
 * @brief Traits class to hold information on types used by the mesh class
 * 
 * @tparam MeshType 
 */
template<typename MeshType>
class MeshTraits{};

/**
 * @brief Common interface for mesh types.
 * 
 * @tparam MeshType Class implementing the interface
 * 
 * 
 * This is not a pure interface class. Because boundary patches share the
 * same implementation across all mesh types, storage of and access to boundary
 * patches is implemented here. Furthermore, the printInfo() member function
 * is implemented here to allow comparable information between different mesh
 * types. All functionality directly implemented here only uses interface 
 * functions and those related to boundary patches.
 * Furthermore, the number of ghost cell layers is stored here directly.
 * 
 * @see EdgeBase, CellBase, StructMeshBase, Mesh
 */
template<typename MeshType>
class MeshBase : public CrtpBase<MeshType>
{
public:
	using Traits = MeshTraits<MeshType>;
	using VertexType = typename Traits::VertexType;
	using EdgeType   = typename Traits::EdgeType;
	using EdgesType  = typename Traits::EdgesType;
	using CellType   = typename Traits::CellType;
	using CellsType  = typename Traits::CellsType;

protected:
	EdgesType m_edges;
	CellsType m_cells;
	/* previously, unordered_map was used. Changed to play better with MPI */
	std::vector<BoundaryPatch> m_boundaryPatches;

	/* The mesh only provides access to the logger, but doesn't own it.
	 * Therefore, changing the logger, e.g. setting a sink's log level,
	 * isn't considered changing the mesh, which is why it's declared mutable.
	 */
	mutable std::shared_ptr<log::logger> m_logger;

public:
	/**************************************************************************/
	/* to be implemented by derived types */
	/**************************************************************************/

	/**
	 * @brief Returns the coordinates of the specified vertex.
	 * 
	 * @return VertexType 
	 */
	auto vertex(Index) const -> VertexType;

	/**
	 * @brief Returns an object whose type implements EdgeBase.
	 * 
	 * @return EdgeType 
	 */
	auto edge(Index) const -> EdgeType;

	/**
	 * @brief Returns an object whose type implements CellBase.
	 * 
	 * @return CellType 
	 */
	auto cell(Index) const -> CellType;
	
	/**
	 * @brief Returns the number of vertices of the mesh.
	 * 
	 * @return Index 
	 */
	auto nVertices() const -> Index;

	/**
	 * @brief Returns the number of edges of the mesh.
	 * 
	 * @return Index 
	 */
	auto nEdges() const -> Index;
	/**
	 * @brief Returns the number of edges which are not boundary edges.
	 * 
	 * @return Index 
	 */
	auto nInnerEdges() const -> Index;
	/**
	 * @brief Returns the number of boundary edges of the mesh.
	 * 
	 * @return Index 
	 */
	auto nBoundaryEdges() const -> Index;
	/**
	 * @brief Returns the number of cells of the mesh (sum of ghost cells and
	 * domain cells)
	 * 
	 * @return Index 
	 */
	auto nCells() const -> Index;
	/**
	 * @brief Returns the number of domain cells
	 * 
	 * @return Index 
	 * 
	 * Domain cells are cells which are fully bounded by edges.
	 */
	auto nDomainCells() const -> Index;
	/**
	 * @brief Returns the number of ghost cells of the mesh.
	 * 
	 * @return Index 
	 */
	auto nGhostCells() const -> Index;
	
	/**
	 * @brief Returns the domain size, i.e. the sum of all domain cells' areas.
	 * 
	 * @return scalar 
	 */
	auto domainArea() const -> scalar;
	

	/**************************************************************************/
	/* implemented here in base */
	/**************************************************************************/

	/**
	 * @brief Returns the EdgesType meta-object, giving access to such
	 * properties as cell indices, lengths and normals
	 * 
	 * @return const EdgesType& 
	 */
	auto edges() const -> const EdgesType&;

	/**
	 * @brief Returns the CellsType meta-object, giving access to
	 * areas, centroids and circumferences
	 * 
	 * @return const CellsType& 
	 */
	auto cells() const -> const CellsType&;


	/** @brief Passes each cell to the Callable argument. Intended for
	 * functions that require parts of the cell interface.
	 * 
	 * @tparam Callable 
	 * @param func 
	 * 
	 * If OpenMP is used, this function parallelises execution of the
	 * Callable by distributing cells to the available threads.
	 * It checks for omp_in_parallel() to prevent nested parallelism
	 * and can therefore safely be called from both serial and parallel regions.
	 * This applies to all its variants as well.
	 * @see forEachDomainCell, forEachCellIndex, forEachGhostCell
	 */
	template<typename Callable>
	void forEachCell( Callable&& func ) const;

	/** @brief Passes each cell to the Callable argument. Intended for
	 * functions that require parts of the cell interface.
	 * 
	 * @tparam Callable 
	 * @param func 
	 * 
	 * @see forEachCell, forEachDomainCellIndex, forEachGhostCellIndex
	 */
	template<typename Callable>
	void forEachCellIndex( Callable&& func ) const;

	/** @brief Passes each domain cell to the Callable argument. Intended for
	 * functions that require parts of the cell interface.
	 * 
	 * @tparam Callable 
	 * @param func 
	 * 
	 * @see forEachCell, forEachDomainCellIndex, forEachGhostCell
	 */
	template<typename Callable>
	void forEachDomainCell( Callable&& func ) const;

	/** @brief Passes each domain cell index to the callable argument. Intended
	 * for functions that only require the cell index.
	 * 
	 * @tparam Callable 
	 * @param func 
	 * 
	 * @see forEachDomainCell, forEachCellIndex, forEachGhostCellIndex
	 */
	template<typename Callable>
	void forEachDomainCellIndex( Callable&& func ) const;

	/** @brief Passes each ghost cell to the callable argument. Intended for
	 * functions that require parts of the cell interface.
	 * 
	 * @tparam Callable 
	 * @param func 
	 * 
	 * @see forEachGhostCellIndex, forEachCell, forEachDomainCell
	 */
	template<typename Callable>
	void forEachGhostCell( Callable&& func ) const;

	/** @brief Passes each ghost cell index to the callable argument. Intended
	 * for functions that only require the cell index.
	 * 
	 * @tparam Callable 
	 * @param func 
	 * 
	 * @see forEachGhostCell, forEachCellIndex, forEachDomainCellIndex
	 */
	template<typename Callable>
	void forEachGhostCellIndex( Callable&& func ) const;


	/** @brief Variant for calls which always come from inside parallel regions.
	 * @see forEachCell */
	template<typename Callable>
	void forEachCell_noNesting( Callable&& func ) const;

	/** @brief Variant for calls which always come from inside parallel regions.
	 * @see forEachCellIndex */
	template<typename Callable>
	void forEachCellIndex_noNesting( Callable&& func ) const;

	/** @brief Variant for calls which always come from inside parallel regions.
	 * @see forEachDomainCell */
	template<typename Callable>
	void forEachDomainCell_noNesting( Callable&& func ) const;

	/** @brief Variant for calls which always come from inside parallel regions.
	 * @see forEachDomainCellIndex */
	template<typename Callable>
	void forEachDomainCellIndex_noNesting( Callable&& func ) const;

	/** @brief Variant for calls which always come from inside parallel regions.
	 * @see forEachGhostCell */
	template<typename Callable>
	void forEachGhostCell_noNesting( Callable&& func ) const;

	/** @brief Variant for calls which always come from inside parallel regions.
	 * @see forEachGhostCellIndex */
	template<typename Callable>
	void forEachGhostCellIndex_noNesting( Callable&& func ) const;

	/**
	 * @brief Returns all boundary patches of the mesh.
	 * 
	 * @return const std::vector<BoundaryPatch>&
	 */
	auto boundaryPatches() const ->
		const std::vector<BoundaryPatch>&;
	
	auto boundaryPatches() ->
		std::vector<BoundaryPatch>&;
	
	/**
	 * @brief Returns the BoundaryPatch with the specified name. Throws
	 * runtime_error if patch does not exist.
	 * 
	 * @param name 
	 * @return const BoundaryPatch& 
	 */
	auto boundaryPatch(const std::string& name) const -> const BoundaryPatch&;
	auto boundaryPatch(const std::string& name) -> BoundaryPatch&;

	/**
	 * @brief Returns the BoundaryPatch with the specified name. Performs no
	 * checks and is therefore faster, but unsafe.
	 * 
	 * @param name 
	 * @return const BoundaryPatch& 
	 */
	auto boundaryPatch_p(const std::string& name) const -> const BoundaryPatch&;
	
	/**
	 * @brief Adds a BoundaryPatch defined by the passed in edges under the 
	 * specified name to the mesh. If the edge indices contain inner
	 * (=non-boundary) edges, a hms::MeshError is thrown.
	 * 
	 * @param name 
	 * @param edges 
	 */
	void addBoundaryPatch( std::string name, ArrayXi&& edges );

	/**
	 * @brief Constructs a BoundaryPatch from the provided arguments and adds it
	 * to the mesh.
	 * 
	 * @param name 
	 * @param edges 
	 * @param domainCells 
	 * @param ghostCells 
	 */
	void addBoundaryPatch(
		std::string name,
		ArrayXi&& edges,
		ArrayXi&& domainCells,
		ArrayXXi&& ghostCells
	);

	/**
	 * @brief Replaces all boundary patches with the ones provided.
	 */
	void addBoundaryPatches(
		std::vector<BoundaryPatch>&&
	);

	/**
	 * @brief Clears all boundary patches.
	 */
	void clearBoundaryPatches();

	/**
	 * @brief Erases the boundary patch of the specified name.
	 * 
	 * @param name 
	 */
	void eraseBoundaryPatch( const std::string& name );

	/**
	 * @brief Prints information on the mesh to std::cout, such as the number of
	 * edges, cells, boundary patches etc.
	 */
	void printInfo() const;

	/**
	 * @brief Returns the number of ghost cell layers surrounding the mesh.
	 */
	constexpr Index nGhostLayers() const { return 1; };

	/**
	 * @brief Returns true if the mesh has no cells and false otherwise.
	 */
	bool empty() const;

	log::logger& logger() const;

protected:
	MeshBase();
	
	MeshBase( MeshBase<MeshType>&& ) noexcept;

	MeshBase<MeshType>& operator=( MeshBase<MeshType>&& ) noexcept;

public:
	/* no copy construction */
	MeshBase( MeshBase<MeshType>&  ) = delete;
	MeshBase( const MeshBase<MeshType>& ) = delete;

	/* to prevent slicing */
	MeshBase<MeshType>& operator=( const MeshBase<MeshType>& ) = delete;
};

} // end namespace hms

#include "meshBase.tpp"

#endif
